package com.chen10.cattle.ws;

import com.alibaba.fastjson.JSON;
import com.chen10.cattle.common.constants.GameConstants;
import com.chen10.cattle.common.constants.RedisKeyConstants;
import com.chen10.cattle.common.constants.WsEventConstants;
import com.chen10.cattle.common.enums.game.RoomStatusEnums;
import com.chen10.cattle.common.enums.game.UserOnlineStatus;
import com.chen10.cattle.common.enums.response.ErrorEnum;
import com.chen10.cattle.common.exception.BusinessException;
import com.chen10.cattle.common.utils.GameUtils;
import com.chen10.cattle.common.utils.JwtUtil;
import com.chen10.cattle.common.utils.RedisUtil;
import com.chen10.cattle.mapper.FriendshipMapper;
import com.chen10.cattle.mapper.GameRecordsMapper;
import com.chen10.cattle.mapper.UserMapper;
import com.chen10.cattle.model.pojo.GameRecords;
import com.chen10.cattle.service.impl.MatchFightServiceImpl;
import com.chen10.cattle.ws.pojo.MoveData;
import com.chen10.cattle.ws.pojo.Room;
import com.chen10.cattle.ws.pojo.WsMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Slf4j
public class WebSocketHandler extends TextWebSocketHandler {

    // 存储在线用户的会话，键为 userId，值为 WebSocketSession
    private static final Map<Integer, WebSocketSession> usersSessions = new ConcurrentHashMap<>();
    @Resource
    private FriendshipMapper friendshipMapper;
    @Resource
    private GameRecordsMapper gameRecordsMapper;
    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisUtil redisUtil;

    public boolean isOnline(Integer userId) {
        return usersSessions.containsKey(userId);
    }


    /**
     * 连接后的操作
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 从 session 中获取 token，解析出 userId，并存储会话
        String token = (String) session.getAttributes().get("token");
        if (token!= null) {
            Integer userId = JwtUtil.getUserIdFromToken(token);
            usersSessions.put(userId, session);
            log.info("用户 {} 已上线，连接 websocket 成功", userId);

            // 将刚上线用户的好友列表中，在线的用户发送给刚上线的用户
            // sendOnlineFriendsToUser(userId);

            // 广播用户上线的消息给他的在线好友
            WsMessage wsMessage = new WsMessage();
            wsMessage.setEvent(WsEventConstants.USER_LOGIN_EVENT);
            broadcastToFriends(userId, wsMessage);

            // 缓存用户状态
            redisUtil.set(RedisKeyConstants.REDIS_USER_STATUS_KEY_PREFIX + userId, UserOnlineStatus.ONLINE.getDesc());

        } else {
            log.warn("无法获取到 token，连接失败");
            session.close(CloseStatus.BAD_DATA);  // 关闭连接
        }
    }


    /**
     * 连接关闭后的操作
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 用户下线时，移除其会话
        String token = (String) session.getAttributes().get("token");
        Integer userId = JwtUtil.getUserIdFromToken(token);
        usersSessions.remove(userId);

        WsMessage wsMessage = new WsMessage();
        wsMessage.setEvent(WsEventConstants.USER_LOGOUT_EVENT);
        broadcastToFriends(userId, wsMessage);
        log.info("用户 {} 已下线，连接websocket关闭", userId);

        // 通知对方玩家该用户已下线
        Object usersRoomId = redisUtil.get(RedisKeyConstants.REDIS_USERS_ROOM_ID_KEY_PREFIX + userId);
        if(usersRoomId != null){
            notifyOpponentUserDisconnected(userId,usersRoomId.toString());
        }

        //删除匹配中的用户
        if(MatchFightServiceImpl.isUserInMatching(userId)){
            MatchFightServiceImpl.removeUserFromMatchUsers(userId);
        }

        //删除缓存
        redisUtil.delete(RedisKeyConstants.REDIS_USER_STATUS_KEY_PREFIX + userId);
        redisUtil.delete(RedisKeyConstants.REDIS_ROOM_KEY_PREFIX + GameConstants.ROOMID_PREFIX + userId);
        redisUtil.delete(RedisKeyConstants.REDIS_USERS_ROOM_ID_KEY_PREFIX + userId);
    }


    /**
     * 处理客户端发来的消息
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        log.info("收到客户端发送的消息：{}", message.getPayload());
        // 使用fastjson解析消息，假设消息格式是JSON，包含目标用户ID和内容及类型
        WsMessage msg = JSON.toJavaObject(JSON.parseObject(message.getPayload()), WsMessage.class);
        Integer senderId = msg.getSenderId();

        //如果是游戏落子事件
        if (WsEventConstants.GAME_MOVE_EVENT.equals(msg.getEvent())) {
            handleGameMoveEvent(msg);
        }

    }


    /**
     * 向指定用户发送消息
     *
     * @param toUserId  目标用户的ID
     * @param wsMessage 要发送的消息对象
     */
    public void sendMessageToUser(Integer toUserId, WsMessage wsMessage) {
        WebSocketSession session = usersSessions.get(toUserId);
        Integer senderId = wsMessage.getSenderId();
        if (session != null && session.isOpen()) {
            try {
                String payload = JSON.toJSONString(wsMessage);
                session.sendMessage(new TextMessage(payload));

                // 简化日志信息
                String logPayload = String.format("event=%s, senderId=%s, data=%s",
                        wsMessage.getEvent(),
                        wsMessage.getSenderId(),
                        wsMessage.getData()
                ); // 同上，假设存在getStatus()方法


                log.info("成功向用户 {} 发送消息: {}", toUserId, logPayload);

                // 如果需要在调试时查看完整的消息内容，可以在 DEBUG 级别下记录完整消息
                // log.debug("完整消息内容: {}", payload);
            } catch (Exception e) {
                log.error("向用户 {} 发送消息时出错", toUserId, e);
            }
        } else {
            log.warn("用户 {} 不在线，无法发送消息", toUserId);
            // 如果用户不在线


        }
    }

    /**
     * 广播消息给在线用户
     *
     * @param userId
     */
    public void broadcastToFriends(Integer userId, WsMessage wsMessage) {
        wsMessage.setSenderId(userId);
        List<Integer> friendIds = friendshipMapper.getFriendIdList(userId);

        for (Integer friendId : friendIds) {
            wsMessage.setTimestamp(System.currentTimeMillis());
            sendMessageToUser(friendId, wsMessage);
        }
    }

    public void broadcastToUsers(List<Integer> userIds, WsMessage wsMessage) {
        for (Integer userId : userIds) {
            sendMessageToUser(userId, wsMessage);
        }
    }


    private void sendOnlineFriendsToUser(Integer userId) {
        // 获取当前用户的在线好友列表
        List<Integer> friendIds = friendshipMapper.getFriendIdList(userId);
        List<Integer> onlineFriendIds = friendIds.stream()
                .filter(usersSessions::containsKey)
                .toList();

        // 向刚上线的用户发送所有在线好友的登录事件
        for (Integer onlineFriendId : onlineFriendIds) {
            WsMessage wsMessage = new WsMessage();
            wsMessage.setEvent(WsEventConstants.USER_LOGIN_EVENT);
            wsMessage.setSenderId(onlineFriendId);
            wsMessage.setTimestamp(System.currentTimeMillis());
            sendMessageToUser(userId, wsMessage);
        }
    }


    /**
     * 处理游戏落子事件
     * @param wsMessage
     */
    private void handleGameMoveEvent(WsMessage wsMessage) {
        Integer senderId = wsMessage.getSenderId();
        try {
            // 解析落子信息
            MoveData moveData = JSON.parseObject(JSON.toJSONString(wsMessage.getData()), MoveData.class);
            if (moveData == null) {
                throw new BusinessException(ErrorEnum.PARAM_ERROR);
            }
            String roomId = moveData.getRoomId();
            Integer userId = wsMessage.getSenderId();
            // 从Redis中获取房间信息
            Room room = (Room) redisUtil.get(RedisKeyConstants.REDIS_ROOM_KEY_PREFIX + roomId);
            if (room == null) {
                throw new BusinessException(ErrorEnum.ROOM_NOT_EXIST);
            }
            if (!room.isRoomPlayer(userId)) {
                throw new BusinessException(ErrorEnum.USER_NOT_IN_ROOM);
            }
            // 确保轮到当前玩家落子
            if (!userId.equals(room.getCurrentPlayerId())) {
                throw new BusinessException(ErrorEnum.NOT_YOUR_TURN);
            }

            // 执行落子逻辑
            Integer row = moveData.getRow();
            Integer col = moveData.getCol();
            int[][] board = room.getBoard();
            int value = room.getCurrentPlayerColor();
            Integer nextPlayerId = GameUtils.getNextPlayerId(room.getPlayerInfoList(), room.getCurrentPlayerId());

            if (!GameUtils.isValidMove(room.getBoard(), Room.BOARD_SIZE, row, col)) {
                throw new BusinessException(ErrorEnum.INVALID_MOVE);
            }
            // 更新棋盘
            board[row][col] = value;
            room.setBoard(board);

            //转发落子给下一个玩家
            WsMessage<MoveData> moveMessage = new WsMessage();
            moveMessage.setEvent(WsEventConstants.NEXT_PLAYER_MOVE_EVENT);
            moveMessage.setSenderId(moveData.getUserId());
            moveMessage.setData(moveData);
            moveMessage.setTimestamp(System.currentTimeMillis());

            sendMessageToUser(nextPlayerId, moveMessage);

            //判断胜负
            // 检查是否胜利
            List<int[]> winPositions = GameUtils.checkWin(room.getBoard(), row, col);
            if(winPositions!=null) {
                //缓存下棋数据
                recordMoveData(moveData);
                //记录数据库
                insertGameRecordToDb(room, room.getCurrentPlayerId(),nextPlayerId);
                //重新初始化房间状态
                room.initGame();
                room.setStatus(RoomStatusEnums.FULL.getDesc());
                //写到redis
                redisUtil.set(RedisKeyConstants.REDIS_ROOM_KEY_PREFIX + roomId, room); // 回写到Redis
                //发送游戏结果消息
                sendGameResultMessageToPlayers(false,userId, nextPlayerId,winPositions);
            }else{
                // 游戏结束，平局
                if (GameUtils.checkBoardFull(board)) {
                    //缓存下棋数据
                    recordMoveData(moveData);
                    //记录数据库
                    insertGameRecordToDb(room,null,null);
                    //重新初始化房间状态
                    room.initGame();
                    room.setStatus(RoomStatusEnums.FULL.getDesc());
                    //写到redis
                    redisUtil.set(RedisKeyConstants.REDIS_ROOM_KEY_PREFIX + roomId, room); // 回写到Redis
                    //发送游戏结果消息
                    sendGameResultMessageToPlayers(true, room.getCurrentPlayerId(), nextPlayerId,null);
                }else{
                    // 更新房间的信息，切换到下一个玩家
                    room.setCurrentPlayerId(nextPlayerId);
                    room.setCurrentPlayerColor(room.getCurrentPlayerColor()== 1?-1:1);
                    redisUtil.set(RedisKeyConstants.REDIS_ROOM_KEY_PREFIX + roomId, room); // 回写到Redis
                    //缓存下棋数据
                    recordMoveData(moveData);
                }
            }

        } catch (BusinessException e) {
            log.error("处理游戏落子事件时出错", e);
            // 向客户端发送错误消息
            WsMessage<String> error = new WsMessage<>();
            error.setSenderId(-666);
            error.setEvent(WsEventConstants.GAME_MOVE_ERROR_EVENT);
            error.setData(e.getMessage());
            error.setTimestamp(System.currentTimeMillis());
            sendMessageToUser(senderId, error);
        }


    }

    /**
     * 发送游戏结果给玩家
     * @param winnerId
     * @param loserId
     */
    private void sendGameResultMessageToPlayers(boolean isDraw, Integer winnerId, Integer loserId,List<int[]> winPositions) {

        if(isDraw){
            WsMessage drawMessage = new WsMessage();
            drawMessage.setEvent(WsEventConstants.GAME_DRAW_EVENT);
            drawMessage.setSenderId(-666);
            drawMessage.setTimestamp(System.currentTimeMillis());

            sendMessageToUser(winnerId, drawMessage);
            sendMessageToUser(loserId, drawMessage);
        }else{
            WsMessage winMessage = new WsMessage();
            winMessage.setEvent(WsEventConstants.GAME_WIN_EVENT);
            winMessage.setSenderId(-666);
            winMessage.setData(winPositions);
            winMessage.setTimestamp(System.currentTimeMillis());

            sendMessageToUser(winnerId, winMessage);

            WsMessage loseMessage = new WsMessage();
            loseMessage.setEvent( WsEventConstants.GAME_LOSE_EVENT);
            loseMessage.setSenderId(-666);
            loseMessage.setData(winPositions);
            loseMessage.setTimestamp(System.currentTimeMillis());

            sendMessageToUser(loserId, loseMessage);
        }

    }
    private void insertGameRecordToDb(Room room,Integer winnerId,Integer loserId) {
        GameRecords gameRecords = new GameRecords();
        gameRecords.setUserId1(room.getPlayerInfoList().get(0).getUserId());
        gameRecords.setUserId2(room.getPlayerInfoList().get(1).getUserId());
        gameRecords.setMoves(redisUtil.get(RedisKeyConstants.REDIS_GAME_DATA_KEY_PREFIX + room.getRoomId()).toString());
        gameRecords.setWinner(winnerId);
        gameRecords.setGameOverTime(new Date());

        gameRecordsMapper.insertGameRecord(gameRecords);

        if(room.isMatchMode() && winnerId != null && loserId != null){
            //更新玩家的分数
            userMapper.addScoreById(winnerId,20);
            userMapper.addScoreById(loserId,-15);
        }

        //删除缓存
        redisUtil.delete(RedisKeyConstants.REDIS_GAME_DATA_KEY_PREFIX + room.getRoomId());
    }

    /**
     * 记录数据
     * @param moveData
     */
    private void recordMoveData(MoveData moveData) {
        String roomId = moveData.getRoomId();
        String data = GameUtils.formatMoveData(moveData);
        String oldData = (String) redisUtil.get(RedisKeyConstants.REDIS_GAME_DATA_KEY_PREFIX + roomId);
        redisUtil.set(RedisKeyConstants.REDIS_GAME_DATA_KEY_PREFIX + roomId, oldData+data); // 回写到Redis
    }



    public void broadcastToFriendsUpdateUserStatus(Integer userId) {
        WsMessage wsMessage = new WsMessage();
        wsMessage.setEvent(WsEventConstants.USER_STATUS_UPDATE_EVENT);
        wsMessage.setTimestamp(System.currentTimeMillis());

        broadcastToFriends(userId, wsMessage);
    }


    /**
     * 通知对手用户已断开连接
     */
    private void notifyOpponentUserDisconnected(Integer userId,String roomId) {
        if (roomId!= null) {
            Room room = (Room) redisUtil.get(RedisKeyConstants.REDIS_ROOM_KEY_PREFIX + roomId);
            if (room!= null) {
                Integer opponentId = getOpponentUserId(room, userId);
                if (opponentId!= null) {
                    Object movesRecords = redisUtil.get(RedisKeyConstants.REDIS_GAME_DATA_KEY_PREFIX + roomId);
                    if (movesRecords != null) {
                        String s = userId + "断开了连接,";
                        redisUtil.set(RedisKeyConstants.REDIS_GAME_DATA_KEY_PREFIX+roomId, movesRecords + s);
                        insertGameRecordToDb(room, opponentId,userId);
                    }

                    WsMessage wsMessage = new WsMessage();
                    wsMessage.setEvent(WsEventConstants.OPPONENT_DISCONNECTED_EVENT);
                    wsMessage.setSenderId(-666);
                    wsMessage.setTimestamp(System.currentTimeMillis());
                    sendMessageToUser(opponentId, wsMessage);

                    //删除缓存
                    redisUtil.delete(RedisKeyConstants.REDIS_GAME_DATA_KEY_PREFIX + room.getRoomId());
                    redisUtil.delete(RedisKeyConstants.REDIS_ROOM_KEY_PREFIX + roomId);

                }
            }
        }
    }


    /**
     * 根据房间和用户 ID 查找对手的用户 ID
     *
     * @param room   房间信息
     * @param userId 当前用户 ID
     * @return 对手的用户 ID
     */
    private Integer getOpponentUserId(Room room, Integer userId) {
        List<Integer> playerIds = room.getPlayerInfoList().stream().map(player -> player.getUserId()).toList();
        if(room.getPlayerInfoList().size()==2){
            if (playerIds.get(0).equals(userId)) {
                return playerIds.get(1);
            } else if (playerIds.get(1).equals(userId)) {
                return playerIds.get(0);
            }
        }

        return null;
    }

}
